package com.antong.cloud.auth.config;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.antong.cloud.common.core.util.R;
import com.antong.cloud.common.security.token.JwtUserAuthenticationConverter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.token.DefaultAccessTokenConverter;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;
import org.springframework.security.oauth2.provider.token.store.JwtTokenStore;
import org.springframework.security.web.access.AccessDeniedHandler;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
 * 认证服务配置
 */
@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter
{

    @Autowired
    AuthenticationManager authenticationManager;

    @Autowired
    RedisConnectionFactory redisConnectionFactory;

    @Autowired
    UserDetailsService userDetailsService;

    @Autowired
    PasswordEncoder passwordEncoder;



    /**
     * 客户端配置
     * @param clients
     * @throws Exception
     */
    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        clients.inMemory()
                .withClient("client01")
                .secret(passwordEncoder.encode("client01_secret"))
                .redirectUris("https://www.baidu.com")
                //有效时间
                .accessTokenValiditySeconds(3000)
                //授权码模式，密码授权模式、刷新令牌
                .authorizedGrantTypes("authorization_code", "implicit", "client_credentials", "refresh_token", "password")
                .scopes( "all");
    }

    @Override
    public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
        // allowFormAuthenticationForClients 是为了注册clientCredentialsTokenEndpointFilter
        // 拦截 /oauth/token 请求，解析request中的client_id和client_secret，并验证
        // 开放 oauth/check_token
        security.checkTokenAccess("permitAll()");
        // 开放 oauth/check_key
        security.tokenKeyAccess("permitAll()");
        security.allowFormAuthenticationForClients();

        security.accessDeniedHandler(new AccessDeniedHandler() {
            @Override
            public void handle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AccessDeniedException e) throws IOException, ServletException {
                httpServletResponse.getWriter().write(new ObjectMapper().writeValueAsString(R.fail(null, e.getMessage())));
            }
        });

        security.passwordEncoder(passwordEncoder);

    }

    /**
     * 令牌管理策略
     * 默认情况下，令牌是通过 randomUUID 产生32位随机数的来进行填充的，而产生的令牌默认是存储在内存中。
     * 内存采用TokenStore 接口的默认实现类 InMemoryTokenStore , 开发时方便调试，适用单机版。
     * RedisTokenStore 将令牌存储到 Redis 非关系型数据库中，适用于并发高的服务。
     * JdbcTokenStore 基于 JDBC 将令牌存储到 关系型数据库中，可以在不同的服务器之间共享令牌。
     * JwtTokenStore （JSON Web Token）将用户信息直接编码到令牌中，这样后端可以不用存储它，前端拿到令牌可以直接解析出用户信息。
     * @param endpoints
     * @throws Exception
     */
    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {

        endpoints
                .tokenStore(tokenStore())
                .accessTokenConverter(accessTokenConverter())
                // 用户获取方式配置
                .userDetailsService(userDetailsService)
                // password 方式获取token必须配置用户认证管理器
                .authenticationManager(authenticationManager);

        /*// 当使用jwt时，需要添加token增强器时，需要使用增强器链
        TokenEnhancerChain tokenEnhancerChain = new TokenEnhancerChain();
        tokenEnhancerChain.setTokenEnhancers(Arrays.asList(new CustomTokenEnhancer(), accessTokenConverter()));
        endpoints.tokenEnhancer(tokenEnhancerChain);*/
    }


    @Bean
    public TokenStore tokenStore() {
        return new JwtTokenStore(accessTokenConverter());
    }

    /**
     * JwtAccessTokenConverter 实现了
     * TokenEnhancer 接口， 负责将普通UUID token增强为 JWT token
     * AccessTokenConverter 接口, 实际上是委托 DefaultAccessTokenConverter 实现token信息转换，token 《》map 相互转换
     * @return
     */
    @Bean
    public JwtAccessTokenConverter accessTokenConverter() {
        JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
        ((DefaultAccessTokenConverter)converter.getAccessTokenConverter()).setUserTokenConverter(new JwtUserAuthenticationConverter());
        converter.setSigningKey("123");
        return converter;
    }



}
